// 
//  
//  Copyright (C) 2009 Autodesk Inc.
//  
//  This library is free software; you can redistribute it and/or
//  modify it under the terms of version 2.1 of the GNU Lesser
//  General Public License as published by the Free Software Foundation.
//  
//  This library is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//  Lesser General Public License for more details.
//  
//  You should have received a copy of the GNU Lesser General Public
//  License along with this library; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//  

#include "stdafx.h"
#include "OgrFdoUtil.h"
#include "MapInfoProvider.h"
#include "MapInfoConnection.h"
#include "MapInfoFeatureSchema.h"
#include "MapInfoClassDefinition.h"

#include <Fdo/Schema/ClassCapabilities.h>
#include <FdoCommonStringUtil.h>
#include <FdoCommonMiscUtil.h>

void tilde2dot(char* mbfc)
{
    //TODO HACK remove . from feature class names -- this is a workaround for Oracle
    for (int i=(int)strlen(mbfc)-1; i>=0; i--)
    {
        if (mbfc[i] == '~')
            mbfc[i] = '.';
    }
}

void dot2tilde(wchar_t* wname)
{
    //TODO HACK remove . from feature class names -- this is a workaround for Oracle
    for (int i=(int)wcslen(wname)-1; i>=0; i--)
    {
        if (wname[i] == '.')
            wname[i] = '~';
    }
}

FdoDataType OgrFdoUtil::TABFieldType2FdoDataType(TABFieldType etype)
{
    FdoDataType dt;
    switch (etype)
    {
        case TABFLogical:
            dt = FdoDataType_Boolean;
            break;
        case TABFSmallInt: 
            dt = FdoDataType_Int16;
            break;
        case TABFInteger: 
            dt = FdoDataType_Int32;
            break;
        case TABFChar:
            dt = FdoDataType_String;
            break;
        case TABFDecimal:
            dt = FdoDataType_Decimal;
            break;
        case TABFFloat:
            dt = FdoDataType_Single;
            break;
        case TABFDate:
        case TABFTime:
        case TABFDateTime:
            dt = FdoDataType_DateTime;
            break;
        default:
            throw FdoException::Create(NlsMsgGetMain(FDO_NLSID(MAPINFOPROVIDER_UNKNOWN_DATA_TYPE)));
    }
    return dt;
}

TABFieldType OgrFdoUtil::FdoDataType2TABFieldType(FdoDataType etype)
{
    TABFieldType ft;
    switch (etype)
    {
        case FdoDataType_Boolean:
            ft = TABFLogical;
            break;
        case FdoDataType_Decimal:
            ft = TABFDecimal;
            break;
        case FdoDataType_Int16:
            ft = TABFSmallInt;
            break;
        case FdoDataType_Int32:
            ft = TABFInteger;
            break;
        case FdoDataType_String:
            ft = TABFChar;
            break;
        case FdoDataType_Single:
            ft = TABFFloat;
            break;
        case FdoDataType_DateTime:
            ft = TABFDateTime;
            break;
        default:
            throw FdoException::Create(NlsMsgGetMain(FDO_NLSID(MAPINFOPROVIDER_UNKNOWN_DATA_TYPE)));
    }
    return ft;
}

FdoGeometricType OgrFdoUtil::OGRwkbGeometryType2FdoGeometricType(OGRwkbGeometryType eType)
{
    FdoGeometricType gt;
    switch (eType)
    {
        case wkbPolygon:
        case wkbMultiPolygon: 
            gt = FdoGeometricType_Surface;
            break;
        case wkbPoint:
        case wkbMultiPoint: 
            gt = FdoGeometricType_Point; 
            break;
        case wkbLineString:
        case wkbMultiLineString: 
            gt = FdoGeometricType_Curve; 
            break;
        default:
            gt = (FdoGeometricType)(FdoGeometricType_Point | FdoGeometricType_Curve | FdoGeometricType_Surface);
            break;
    }
    return gt;
}

OGRField* OgrFdoUtil::FdoDataValue2OGRField(FdoDataValue* value)
{
    if (NULL == value || value->IsNull())
        return NULL;

    OGRField* fieldValue = new OGRField;
    switch (value->GetDataType())
    {
        case FdoDataType_Boolean:
            fieldValue->Integer = dynamic_cast<FdoBooleanValue*>(value)->GetBoolean();
            break;
        case FdoDataType_Int16:
            fieldValue->Integer = dynamic_cast<FdoInt16Value*>(value)->GetInt16();
            break;
        case FdoDataType_Int32:
            fieldValue->Integer = dynamic_cast<FdoInt32Value*>(value)->GetInt32();
            break;
        case FdoDataType_Single:
            fieldValue->Real = dynamic_cast<FdoSingleValue*>(value)->GetSingle();
            break;
        case FdoDataType_Decimal:
            fieldValue->Real = dynamic_cast<FdoDecimalValue*>(value)->GetDecimal();
            break;
        case FdoDataType_Double:
            fieldValue->Real = dynamic_cast<FdoDoubleValue*>(value)->GetDouble();
            break;
        case FdoDataType_String:
            {
                FdoString* str = dynamic_cast<FdoStringValue*>(value)->GetString();
                W2A(str);
                char* temp = (char*)(malloc(strlen(mbstr) + 1));
                strcpy(temp, mbstr);
                fieldValue->String = temp;
            }
            break;
        case FdoDataType_DateTime:
            {
                FdoDateTime dateTime = dynamic_cast<FdoDateTimeValue*>(value)->GetDateTime();
                fieldValue->Date.Year = dateTime.year;
                fieldValue->Date.Month = dateTime.month;
                fieldValue->Date.Day = dateTime.day;
                fieldValue->Date.Hour = dateTime.hour;
                fieldValue->Date.Minute = dateTime.minute;
                fieldValue->Date.Second = (unsigned char)dateTime.seconds;
            }
            break;
        default:
            delete fieldValue;
            throw FdoException::Create(NlsMsgGetMain(FDO_NLSID(MAPINFOPROVIDER_UNKNOWN_DATA_TYPE)));
    }
    return fieldValue;
}

FdoDataPropertyDefinition* OgrFdoUtil::OGRFieldDefn2FdoDataPropertyDefinition(OGRFieldDefn* field, TABFieldType etype)
{
    const char* PropName = field->GetNameRef();
    A2W(PropName);

    FdoPtr<FdoDataPropertyDefinition> dpd = FdoDataPropertyDefinition::Create(wPropName, L"");
    const OGRField* defaultValue = field->GetDefaultRef();
#pragma message("TODO: Handle default value correctly.")
    //if (defaultValue)
    //{
    //    FdoPtr<FdoDataValue> fdoDefaultValue = OGRField2FdoDataValue(defaultValue, field->GetType());
    //    if (fdoDefaultValue.p)
    //        dpd->SetDefaultValue(fdoDefaultValue->ToString());
    //}
    FdoDataType dt = TABFieldType2FdoDataType(etype);
    dpd->SetDataType(dt);
    if(dt == FdoDataType_Decimal)
    {
        // The precision in FDO means the total number of digits.
        // The precision in MITAB means the number of digits to the right of decimal point.
        // The width in MITAB means the number of digits, decimals points, and optional sign.
        dpd->SetPrecision(field->GetWidth() - 2);
        dpd->SetScale(field->GetPrecision());
    }   
    else
    {
        dpd->SetLength(field->GetWidth());
        dpd->SetPrecision(field->GetPrecision());
    }

    return dpd.Detach();
}

FdoClassDefinition* OgrFdoUtil::MapInfoLayer2FdoClassDefinition(IMapInfoFile* layer)
{
    OGRFeatureDefn* fdefn = layer->GetLayerDefn();

    const char* name = fdefn->GetName();
    A2W(name);

    FdoPtr<FdoFeatureClass> fc = FdoFeatureClass::Create(wname, L"");
    FdoPtr<FdoPropertyDefinitionCollection> pdc = fc->GetProperties();

    // data properties (attributes)
    int propcount = fdefn->GetFieldCount();
    for (int j=0; j<propcount; j++)
    {
        OGRFieldDefn* field = fdefn->GetFieldDefn(j);
        TABFieldType etype = layer->GetNativeFieldType(j);
        FdoPtr<FdoDataPropertyDefinition> dpd = OGRFieldDefn2FdoDataPropertyDefinition(field, etype);
        pdc->Add(dpd);
    }

    // geometry property
    FdoPtr<FdoGeometricPropertyDefinition> gpd;
    const char* geomname = layer->GetGeometryColumn();
    if (NULL != geomname && geomname[0] != 0)
    {
        A2W(geomname);
        gpd = FdoGeometricPropertyDefinition::Create(wgeomname, L"");
    }
    else
        gpd = FdoGeometricPropertyDefinition::Create(DEFAULT_GEOMETRY_PROPERTY_NAME, L"");
    OGRwkbGeometryType gt = fdefn->GetGeomType();
    gpd->SetGeometryTypes(OGRwkbGeometryType2FdoGeometricType(gt));

    // Spatial context is qualified class name.
    FdoStringP spatialContextName;
    TABFileClass fileType = layer->GetFileClass();
    switch(fileType)
    {
        case TABFC_TABFile:
            spatialContextName = TAB_SCHEMA_NAME;
            spatialContextName += L":";
            spatialContextName += wname;
            break;
        case TABFC_TABView:
            spatialContextName = VIEW_SCHEMA_NAME;
            spatialContextName += L":";
            spatialContextName += wname;
            break;
        case TABFC_TABSeamless:
            spatialContextName = SEAMLESS_SCHEMA_NAME;
            spatialContextName += L":";
            spatialContextName += wname;
            break;
        case TABFC_MIFFile:
            spatialContextName = MIF_SCHEMA_NAME;
            spatialContextName += L":";
            spatialContextName += wname;
            break;
        default:
            break;
    }
    gpd->SetSpatialContextAssociation(spatialContextName);

    pdc->Add(gpd);
#pragma message("TODO: Check whether there is an existing geometry property before setting it.")
    fc->SetGeometryProperty(gpd);

    // identity property
    FdoPtr<FdoDataPropertyDefinition> fid;
    const char* idname = layer->GetFIDColumn();
    if (NULL != idname && idname[0] != 0)
    {
        A2W(idname);
        fid = (FdoDataPropertyDefinition*)pdc->FindItem(widname);
        if (!fid.p)
        {
            fid = FdoDataPropertyDefinition::Create(widname, L"");
            fid->SetDataType(FdoDataType_Int32); //TODO should we use Int64?
            pdc->Add(fid);
        }
    }
    else
    {
        fid = (FdoDataPropertyDefinition*)pdc->FindItem(DEFAULT_FEATURE_ID_PROPERTY_NAME);
        if (!fid.p)
        {
            fid = FdoDataPropertyDefinition::Create(DEFAULT_FEATURE_ID_PROPERTY_NAME, L"");
            fid->SetDataType(FdoDataType_Int32); //TODO should we use Int64?
            pdc->Insert(0, fid);
        }
    }

    fid->SetIsAutoGenerated(true);
    //set the ID property of the feature class
    FdoPtr<FdoDataPropertyDefinitionCollection> idpdc = fc->GetIdentityProperties();
    idpdc->Add(fid);

    FdoPtr<FdoClassCapabilities> classCapabilities = FdoClassCapabilities::Create(*fc.p);

    // Set vertex order and strictness rule for geometry property
    classCapabilities->SetPolygonVertexOrderRule(gpd->GetName(), FdoPolygonVertexOrderRule_CCW);
    classCapabilities->SetPolygonVertexOrderStrictness(gpd->GetName(), true);

    classCapabilities->SetSupportsLocking(false);
    classCapabilities->SetSupportsLongTransactions(false);
    int nWritable = !layer->IsFileReadOnly();
    if (nWritable)
        classCapabilities->SetSupportsWrite(true);
    else
        classCapabilities->SetSupportsWrite(false);
    fc->SetCapabilities(classCapabilities);

    return fc.Detach();
}

MapInfoFeatureSchemaCollection* OgrFdoUtil::MapInfoDataStore2SchemaCollection(MapInfoConnection* conn)
{
    VALIDATE_POINTER(conn);

    OGRTABDataSource* datastore = conn->GetOGRDataSource();
    VALIDATE_POINTER(datastore);

    FdoPtr<FdoFeatureSchema> fdoTabSchema = FdoFeatureSchema::Create(TAB_SCHEMA_NAME, L"");
    FdoPtr<FdoFeatureSchema> fdoMifSchema = FdoFeatureSchema::Create(MIF_SCHEMA_NAME, L"");
    FdoPtr<FdoFeatureSchema> fdoSeamlessSchema = FdoFeatureSchema::Create(SEAMLESS_SCHEMA_NAME, L"");
    FdoPtr<FdoClassCollection> fdoTabClasses = fdoTabSchema->GetClasses();
    FdoPtr<FdoClassCollection> fdoMifClasses = fdoMifSchema->GetClasses();
    FdoPtr<FdoClassCollection> fdoSeamlessClasses = fdoSeamlessSchema->GetClasses();

    FdoPtr<MapInfoClassDefinitionCollection> mapinfoTabClasses = new MapInfoClassDefinitionCollection();
    FdoPtr<MapInfoClassDefinitionCollection> mapinfoMifClasses = new MapInfoClassDefinitionCollection();
    FdoPtr<MapInfoClassDefinitionCollection> mapinfoSeamlessClasses = new MapInfoClassDefinitionCollection();
    int count = datastore->GetLayerCount();
    for (int i=0; i<count; i++)
    {
        IMapInfoFile* layer = datastore->GetMapInfoLayer(i);
        if (layer)
        {
            TABFileClass fileType = layer->GetFileClass();
            FdoPtr<FdoClassDefinition> fdoClassDef = MapInfoLayer2FdoClassDefinition(layer);
            FdoPtr<MapInfoClassDefinition> mapinfoClassDef = new MapInfoClassDefinition(conn, layer, fdoClassDef);
            switch (fileType)
            {
                case TABFC_TABFile:
                    fdoTabClasses->Add(fdoClassDef);
                    mapinfoTabClasses->Add(mapinfoClassDef);
                    break;
                case TABFC_TABSeamless:
                    fdoSeamlessClasses->Add(fdoClassDef);
                    mapinfoSeamlessClasses->Add(mapinfoClassDef);
                    break;
                case TABFC_MIFFile:
                    fdoMifClasses->Add(fdoClassDef);
                    mapinfoMifClasses->Add(mapinfoClassDef);
                    break;
                default:
                    break;
            }
        }
    }

    FdoPtr<MapInfoFeatureSchemaCollection> mapinfoSchemas = new MapInfoFeatureSchemaCollection();

    bool bConnectToFile = conn->IsConnectToFile();
    if (bConnectToFile)
    {
        if (mapinfoTabClasses->GetCount() > 0)
        {
            fdoTabSchema->AcceptChanges();
            FdoPtr<MapInfoFeatureSchema> mapinfoTabSchema = new MapInfoFeatureSchema(conn, fdoTabSchema, mapinfoTabClasses);
            mapinfoSchemas->Add(mapinfoTabSchema);
        }
        else if (mapinfoMifClasses->GetCount() > 0)
        {
            fdoMifSchema->AcceptChanges();
            FdoPtr<MapInfoFeatureSchema> mapinfoMifSchema = new MapInfoFeatureSchema(conn, fdoMifSchema, mapinfoMifClasses);
            mapinfoSchemas->Add(mapinfoMifSchema);
        }
        else if (mapinfoSeamlessClasses->GetCount() > 0)
        {
            fdoSeamlessSchema->AcceptChanges();
            FdoPtr<MapInfoFeatureSchema> mapinfoSeamlessSchema = new MapInfoFeatureSchema(conn, fdoSeamlessSchema, mapinfoSeamlessClasses);
            mapinfoSchemas->Add(mapinfoSeamlessSchema);
        }
    }
    else
    {
        fdoTabSchema->AcceptChanges();
        FdoPtr<MapInfoFeatureSchema> mapinfoTabSchema = new MapInfoFeatureSchema(conn, fdoTabSchema, mapinfoTabClasses);
        mapinfoSchemas->Add(mapinfoTabSchema);

        fdoMifSchema->AcceptChanges();
        FdoPtr<MapInfoFeatureSchema> mapinfoMifSchema = new MapInfoFeatureSchema(conn, fdoMifSchema, mapinfoMifClasses);
        mapinfoSchemas->Add(mapinfoMifSchema);

        fdoSeamlessSchema->AcceptChanges();
        FdoPtr<MapInfoFeatureSchema> mapinfoSeamlessSchema = new MapInfoFeatureSchema(conn, fdoSeamlessSchema, mapinfoSeamlessClasses);
        mapinfoSchemas->Add(mapinfoSeamlessSchema);
    }

    return mapinfoSchemas.Detach();
}

long OgrFdoUtil::InsertFeature(FdoPropertyValueCollection* propvals, IMapInfoFile* layer)
{
    //create the new feature
    OGRFeature* feature = new OGRFeature(layer->GetLayerDefn());

    //set all the properties
    ConvertFeature(propvals, feature, layer);

    // Check the feature extent. If it is bigger than the bounds of the tab file, throw an exception
    OGRGeometry * pGeom = feature->GetGeometryRef();
    if(pGeom)
    {
        OGREnvelope envelope;
        pGeom->getEnvelope(&envelope);

        double xMin, xMax, yMin, yMax;
        if(layer->GetBounds(xMin, yMin, xMax, yMax) == 0)
        {
            if(!(envelope.MinX >= xMin && envelope.MaxX <= xMax && envelope.MinY >= yMin && envelope.MaxY <= yMax ))
            {
                 throw FdoCommandException::Create(NlsMsgGetMain(FDO_NLSID(MAPINFOPROVIDER_OUT_OF_BOUNDS)));
            }
        }
    }

    long nFID = layer->WriteFeature(feature);
    OGRFeature::DestroyFeature(feature);
    return nFID;
}

FdoString* OgrFdoUtil::GeometryTypeToString(FdoGeometryType geomType)
{
    switch(geomType)
    {
        case FdoGeometryType_None:
            return L"Unknown";
            break;
        case FdoGeometryType_Point:
            return L"Point";
            break;
        case FdoGeometryType_LineString:
            return L"LineString";
            break;
        case FdoGeometryType_Polygon:
            return L"Polygon";
            break;
        case FdoGeometryType_MultiPoint:
            return L"MultiPoint";
            break;
        case FdoGeometryType_MultiLineString:
            return L"MultiLineString";
            break;
        case FdoGeometryType_MultiPolygon:
            return L"MultiPolygon";
            break;
        case FdoGeometryType_MultiGeometry:
            return L"MultiGeometry";
            break;
        case FdoGeometryType_CurveString:
            return L"CurveString";
            break;
        case FdoGeometryType_CurvePolygon:
            return L"CurvePolygon";
            break;
        case FdoGeometryType_MultiCurveString:
            return L"MultiCurveString";
            break;
        case FdoGeometryType_MultiCurvePolygon:
            return L"MultiCurvePolygon";
            break;
        default:
            return L"Unknown";
            break;
    }
    return L"Unknown";
}

void OgrFdoUtil::CheckGeometryType(FdoGeometryType geomType)
{
    if (geomType != FdoGeometryType_Point &&
        geomType != FdoGeometryType_LineString &&
        geomType != FdoGeometryType_Polygon &&
        geomType != FdoGeometryType_MultiPoint &&
        geomType != FdoGeometryType_MultiLineString &&
        geomType != FdoGeometryType_MultiPolygon &&
        geomType != FdoGeometryType_MultiGeometry)
        throw FdoCommandException::Create(NlsMsgGetMain(MAPINFOPROVIDER_GEOMETRY_NOT_SUPPORTED, "Geometry type '%1$ls' is not supported by MapInfo provider.", GeometryTypeToString(geomType)));
}

void OgrFdoUtil::AddValue(FdoString* propName, FdoValueExpression* value, OGRFeature* dst, IMapInfoFile* layer)
{
    W2A(propName);
    FdoGeometryValue* gv = dynamic_cast<FdoGeometryValue*>(value);
    if (gv)
    {
        if (!gv->IsNull())
        {
            FdoPtr<FdoByteArray> fgf = gv->GetGeometry();
            if (fgf.p && fgf->GetCount() > 0)
            {
                FdoPtr<FdoFgfGeometryFactory> fgfFactory = FdoFgfGeometryFactory::GetInstance();
                FdoPtr<FdoIGeometry> fdoGeom = fgfFactory->CreateGeometryFromFgf(fgf);
                if (fdoGeom.p)
                {
                    FdoGeometryType geomType = fdoGeom->GetDerivedType();
                    CheckGeometryType(geomType);

                    unsigned char* wkb = new unsigned char[fgf->GetCount()]; //WKB is smaller than FGF due to lack of dimensionality flag

                    int len = Fgf2Wkb(fgf->GetData(), wkb);

                    OGRGeometry* orgGeom = NULL;
                    OGRGeometryFactory::createFromWkb(wkb, NULL, &orgGeom, len);

                    dst->SetGeometryDirectly(orgGeom); //the feature now owns this pointer

                    delete [] wkb;
                }
            }
        }
    }
    else
    {
        //or is it a data property?
        OGRFeatureDefn* fdefn = layer->GetLayerDefn();
        int nFieldIndex = fdefn->GetFieldIndex(mbpropName);
        OGRFieldDefn* field = fdefn->GetFieldDefn(nFieldIndex);

        FdoDataType dt = (FdoDataType)-1;
        TABFieldType etype = layer->GetNativeFieldType(nFieldIndex);

        switch (etype)
        {
        case TABFLogical:
            {
                // OGR doesn't support boolean type. It uses character 'T' to represent true.
                // Other characters represents false.
                FdoBooleanValue* iv = dynamic_cast<FdoBooleanValue*>(value);
                if (iv && !iv->IsNull())
                {
                    if (iv->GetBoolean())
                        dst->SetField(mbpropName, "T");
                    else
                        dst->SetField(mbpropName, "F");
                }
            }
            break;
        case TABFSmallInt:
            {
                FdoInt16Value* iv = dynamic_cast<FdoInt16Value*>(value);
                if (iv && !iv->IsNull()) 
                    dst->SetField(mbpropName, (short)iv->GetInt16());
            }
            break;
        case TABFInteger:
            {
                FdoInt32Value* iv = dynamic_cast<FdoInt32Value*>(value);
                if (iv && !iv->IsNull()) 
                    dst->SetField(mbpropName, (int)iv->GetInt32());
            }
            break;
        case TABFChar:
            {
                FdoStringValue* sv = dynamic_cast<FdoStringValue*>(value);
                if (sv && !sv->IsNull())
                {
                    FdoString* str = sv->GetString();
                    W2A(str);
                    dst->SetField(mbpropName, (const char*)mbstr);
                }
            }
            break;
        case TABFFloat:
            {
                FdoSingleValue* iv = dynamic_cast<FdoSingleValue*>(value);
                if (iv && !iv->IsNull()) 
                    dst->SetField(mbpropName, (float)iv->GetSingle());
            }
            break;
        case TABFDecimal:
            {
                FdoDecimalValue* iv = dynamic_cast<FdoDecimalValue*>(value);
                if (iv && !iv->IsNull()) 
                {
                    OGRFieldDefn *pFieldDef = dst->GetFieldDefnRef(dst->GetFieldIndex(mbpropName));
                    double d =  iv->GetDecimal();
                    static double power[10] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000};
                    d = floor(d * power[pFieldDef->GetPrecision()]) / power[pFieldDef->GetPrecision()];
                    
                    dst->SetField(mbpropName, d);
                }
            }
            break;
        case TABFDate:
        case TABFTime:
        case TABFDateTime:
            {
                FdoDateTimeValue* dv = dynamic_cast<FdoDateTimeValue*>(value);

                if (dv && !dv->IsNull())
                {
                    FdoDateTime dt = dv->GetDateTime();

                    if (dt.IsDate())
                    {
                        dst->SetField(mbpropName, dt.year, dt.month, dt.day);
                    }
                    else if (dt.IsDateTime())
                    {
                        dst->SetField(mbpropName, dt.year, dt.month, dt.day, dt.hour, dt.minute, (int)dt.seconds);
                    }
                    else if (dt.IsTime())
                    {
                        dst->SetField(mbpropName, 0, 0, 0, dt.hour, dt.minute, (int)dt.seconds);
                    }
                }
            }
            break;
        default: 
            throw FdoException::Create(NlsMsgGetMain(FDO_NLSID(MAPINFOPROVIDER_UNKNOWN_DATA_TYPE)));
            break;
        }
    }
}

//copies feature properties from an FDO propety value collection
//into an OGR feature -- used for inserting new feature and updating
//existing ones
void OgrFdoUtil::ConvertFeature(FdoPropertyValueCollection* src, OGRFeature* dst, IMapInfoFile* layer)
{
    //update the feature properties
    //this code is not fast at all
    //since we could in theory pre-convert all the property values
    //and then apply them to all the featues, but in the most
    //common case we are only updating one feature so it's a wash.
    for (int i=src->GetCount()-1; i>=0; i--)
    {
        //convert propvals
        FdoPtr<FdoPropertyValue> pv = src->GetItem(i);
        FdoString* propName = FdoPtr<FdoIdentifier>(pv->GetName())->GetName();
        FdoPtr<FdoValueExpression> value = pv->GetValue();
        if (value.p != NULL)
            AddValue(propName, value, dst, layer);
    }
}

//copies feature properties from an FDO propety value collection
//into an OGR feature -- used for inserting new feature and updating
//existing ones
void OgrFdoUtil::ConvertFeature(FdoParameterValueCollection* src, OGRFeature* dst, IMapInfoFile* layer)
{
    //update the feature properties
    //this code is not fast at all
    //since we could in theory pre-convert all the property values
    //and then apply them to all the featues, but in the most
    //common case we are only updating one feature so it's a wash.
    for (int i=src->GetCount()-1; i>=0; i--)
    {
        //convert propvals
        FdoPtr<FdoParameterValue> pv = src->GetItem(i);
        FdoPtr<FdoIdentifier> identifier = FdoIdentifier::Create(pv->GetName());
        FdoString* propName = identifier->GetName();
        FdoPtr<FdoValueExpression> value = pv->GetValue();
        if (value.p != NULL)
            AddValue(propName, value, dst, layer);
    }
}

int OgrFdoUtil::Wkb2Fgf(const unsigned char* wkb, unsigned char* fgf)
{
    OgrBinaryReader src(wkb);
    OgrBinaryWriter dst(fgf);

    src.ReadByte(); //skip byte order -- FGF is always NDR

    // the geometry type
    int geom_type = src.ReadInt();

    // the coordinate type
    int skip = (geom_type < 0) ? 1 : 0; //0=XY, 1=XYZ or XYM, 2 = XYZM
    geom_type = geom_type & 0x7FFFFFFF; //now convert to FGF geom type

    dst.WriteInt(geom_type); //strip out WKB 2.5D indicator (FDO doesn't use that)

    bool is_multi = (geom_type == FdoGeometryType_MultiLineString)
        || (geom_type == FdoGeometryType_MultiPolygon
        || (geom_type == FdoGeometryType_MultiPoint));

    int num_geoms = 1;

    //in case of multipolygon or multilinestring or multipoint,
    //read poly or linestring count
    if (is_multi) 
    {
        num_geoms = src.ReadInt();
        dst.WriteInt(num_geoms);
    }

    for (int q=0; q<num_geoms; q++)
    {
        if (is_multi)
        {
            //skip byte order
            src.ReadByte();

            //geom type
            geom_type = src.ReadInt();
            skip = geom_type < 0 ? 1 : 0; //is it 2.5D
            geom_type &= 0x7FFFFFFF;
            dst.WriteInt(geom_type);
        }

        //read cordinate type
        int dim = skip ? FdoDimensionality_XY | FdoDimensionality_Z : FdoDimensionality_XY;
        dst.WriteInt(dim);

        // the number of contours in current polygon/linestring
        int contour_count = 1; //for linestrings

        if ((geom_type == FdoGeometryType_Polygon)
            || (geom_type == FdoGeometryType_MultiPolygon))
        {
            contour_count = src.ReadInt();
            dst.WriteInt(contour_count);
        }

        for (int i=0; i<contour_count; i++)
        {
            int point_count = 1;

            //point geoms do not have a point count, since
            //each piece is just one point each
            if ((geom_type != FdoGeometryType_MultiPoint)
                && (geom_type != FdoGeometryType_Point))
            {
                point_count = src.ReadInt();
                dst.WriteInt(point_count);
            }

            int numd = point_count * (skip + 2);
            for (int j=0; j<numd; j++) dst.WriteDouble(src.ReadDouble());
        }
    }

    return dst.GetLength();
}


int OgrFdoUtil::Fgf2Wkb(const unsigned char* fgf, unsigned char* wkb)
{
    OgrBinaryReader src(fgf);
    OgrBinaryWriter dst((unsigned char*)wkb);

    dst.WriteByte(1); //NDR

    // the geometry type
    int geom_type = src.ReadInt();

    //we will need to do a second pass to set the 3D flag on this once we read the FDO dimensionality flag
    dst.WriteInt(geom_type); 

    //The FDO constants match WKB, so we don't really need to use the OGR wkb defines
    bool is_multi = (geom_type == FdoGeometryType_MultiLineString)
        || (geom_type == FdoGeometryType_MultiPolygon
        || (geom_type == FdoGeometryType_MultiPoint));

    int num_geoms = 1;

    //in case of multipolygon or multilinestring or multipoint,
    //read poly or linestring count
    if (is_multi) 
    {
        num_geoms = src.ReadInt();
        dst.WriteInt(num_geoms);
    }

    for (int q=0; q<num_geoms; q++)
    {
        if (is_multi)
        {
            //set byte order
            dst.WriteByte(1);

            //geom type
            geom_type = src.ReadInt();
            dst.WriteInt(geom_type);
        }

        //read cordinate type
        int dim = src.ReadInt();
        int skip = ((dim & FdoDimensionality_Z) != 0) ? 1 : 0;
        skip += (dim & FdoDimensionality_M) ? 1 : 0;   // for M value;

        if (skip)
        {
            //roll back and set the geom_type to wkb 2.5D
            //TODO: do we need to set both the geom type for the parent multi geom
            //and the child geom? For example in the case of a multipolygon, do we need to 
            //set it for both the current polygon and the parent multipolygon?
            *(int*)(dst.m_begin+1) = geom_type | 0x80000000; 
        }
        
        // the number of contours in current polygon/linestring
        int contour_count = 1; //for linestrings

        if ((geom_type == FdoGeometryType_Polygon)
            || (geom_type == FdoGeometryType_MultiPolygon))
        {
            contour_count = src.ReadInt();
            dst.WriteInt(contour_count);
        }

        for (int i=0; i<contour_count; i++)
        {
            int point_count = 1;

            //point geoms do not have a point count, since
            //each piece is just one point each
            if ((geom_type != FdoGeometryType_MultiPoint)
                && (geom_type != FdoGeometryType_Point))
            {
                point_count = src.ReadInt();
                dst.WriteInt(point_count);
            }

            for(int j = 0; j < point_count; j ++)
            {
                dst.WriteDouble(src.ReadDouble()); /* X value */
                dst.WriteDouble(src.ReadDouble()); /* Y value */
                if(skip > 0)
                    dst.WriteDouble(src.ReadDouble()); /* Z value */
                if(skip == 2)
                    src.ReadDouble(); /*Skip M value*/
            }
        }
    }

    return dst.GetLength();
}

FdoIGeometry* OgrFdoUtil::OGRGeometryToFdoGeometry(OGRGeometry* geom)
{
    size_t wkblen = geom->WkbSize();
 
    unsigned char* fgf = new unsigned char[wkblen*2];
    unsigned char* wkb = new unsigned char[wkblen];

    geom->exportToWkb(wkbNDR, (unsigned char*)wkb);
    
    FdoPtr<FdoFgfGeometryFactory> factory = FdoFgfGeometryFactory::GetInstance ();
    int fgflen = OgrFdoUtil::Wkb2Fgf(wkb, fgf);
    FdoPtr<FdoByteArray> byteArray = FdoByteArray::Create((unsigned char*)fgf, fgflen);
    FdoIGeometry* fdogeom = factory->CreateGeometryFromFgf(byteArray);
    return fdogeom;
}

void OgrFdoUtil::ArrayToVector(long* ary, recno_list* vec)
{
    if (ary != NULL && vec != NULL)
    {
        int i = 0;
        while(ary[i] != OGRNullFID)
        {
            vec->push_back(ary[i]);
            i++;
        }
    }
}

